#!/usr/bin/env python
#
# SdrScript - User Interface for Control functions
# 
# User: bob
# Date: 24/04/14
# Copyright (C) 2014 by G3UKB Bob Cowdery
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#    
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#    
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#    
#  The author can be reached by email at:   
#     bob@bobcowdery.plus.com
#

# System imports
import os,sys
from PyQt4 import QtCore, QtGui
import pickle
import urllib
import traceback
import subprocess
from time import sleep
import logging

# Application imports
from common.defs import *
from common import persist
from common import broker
import common.instance_cache as instance_cache
from api.api import DSPChannelApi
import ui.control.ip9258 as ip9258
import ui.control.control_config as control_config
from ui.control.control_common import *
from ui.receiver import rx_ui as receiver
from ui.transmitter import tx_ui as transmitter

"""
GUI UI fragment for main control functions
"""
class Control(QtGui.QMainWindow):
    
    STATE_FILE = 'control.state'
    CONFIG_FILE = 'control.cfg'
    
    def __init__(self, metis_imp, q, test_mode):
        """
        Constructor
        
        Arguments:
            metis_imp   -- Metis implementation instance
            q           -- Q to send messages to
            test_mode   -- UI development, don't actually start any real processing
        """
        
        super(Control, self).__init__()
        
        self.__metis_imp = metis_imp
        self.__q = q
        self.__test_mode = test_mode
        
        # Set the back colour
        palette = QtGui.QPalette()
        palette.setColor(QtGui.QPalette.Background,QtGui.QColor(43,63,68,255))
        self.setPalette(palette)
        
        # Retrieve the state settings
        self.__state = persist.getSavedState(Control.STATE_FILE)
        if self.__state == None:
            # Apply defaults
            self.__state = DEFAULT_STATE
            persist.saveState(Control.STATE_FILE, self.__state)
        # Retrieve the config settings
        self.__cfg = persist.getSavedCfg(Control.CONFIG_FILE)
        if self.__cfg == None:
            # Apply defaults
            self.__cfg = DEFAULT_CONFIG
            persist.saveCfg(Control.CONFIG_FILE, self.__cfg)
        instance_cache.addToCache('Settings', self.__cfg)

        # Initialise the GUI
        self.initUI()
        
        # Initialise variables
        self.__connected = False
        self.__remotePowered = False
        self.__goSdrRunning = False
        # Of the form {CH_RX: {rx-id:dsp-api-inst, rx-id:dsp-api-inst}, CH_TX: {tx_id:dsp-api-inst}}
        self.__channels = {CH_RX: {}, CH_TX: {}}
        # Of the form {rx_id: display-api-inst, ...}
        self.__displays = {}
        self.__transmitters = []
        self.__receivers = []
        self.__control_api = None
        self.__metis_api = None
        self.__running = INITIAL
        self.__hints = []
        self.__metisWasRunning = False
        self.__metis_addr = None
        self.__got_addr = False

        # Subscribe to events
        broker.subscribe(self, RESET_EVNT)
        
    def initUI(self):
        """ Configure the GUI interface """
        
        self.setToolTip('SdrScript Control Interface')
        self.statusBar().showMessage('No messages')
        QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 10))
        
        # Arrange window
        self.move(self.__state[WINDOW_X], self.__state[WINDOW_Y])
        if self.__state[WINDOW_WIDTH] == -1 or self.__state[WINDOW_HEIGHT] == -1:
            self.__state[WINDOW_WIDTH] = self.__width = self.width()
            self.__state[WINDOW_HEIGHT] = self.__height = self.height()
        else:
            self.__width = self.__state[WINDOW_WIDTH]
            self.__height = self.__state[WINDOW_HEIGHT]
            self.resize(self.__width, self.__height)
        self.setWindowTitle('SdrScript')
        
        exitAction = QtGui.QAction(QtGui.QIcon('exit.png'), '&Exit', self)        
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.quit)
        configAction = QtGui.QAction(QtGui.QIcon('config.png'), '&Configure', self)        
        configAction.setShortcut('Ctrl+F')
        configAction.setStatusTip('Configure controller')
        configAction.triggered.connect(self.__configEvnt)
        
        menubar = self.menuBar()
        configMenu = menubar.addMenu('&Options')
        configMenu.addAction(configAction)
        
        w = QtGui.QWidget()
        self.setCentralWidget(w)
        grid = QtGui.QGridLayout()
        w.setLayout(grid)
        
        self.startbtn = QtGui.QPushButton('Start', self)
        self.startbtn.setToolTip('Start HPSDR')
        self.startbtn.resize(self.startbtn.sizeHint())
        self.startbtn.setMinimumHeight(30)
        self.startbtn.setEnabled(False)
        self.startbtn.setCheckable(True)
        self.startbtn.setStyleSheet("QPushButton {background-color: rgb(140,21,38); font: bold 20px}")
        grid.addWidget(self.startbtn, 0, 0)
        self.startbtn.clicked.connect(self.__toggleStart)
        
        quitbtn = QtGui.QPushButton('Quit', self)
        quitbtn.clicked.connect(self.quit)
        quitbtn.setToolTip('Quit SdrScript')
        quitbtn.resize(quitbtn.sizeHint())
        quitbtn.setMinimumHeight(30)
        grid.addWidget(quitbtn, 1, 0)
        
        self.transcb = QtGui.QCheckBox('Transcript', self)
        self.transcb.stateChanged.connect(self.__transcriptEvnt)
        self.transcb.setToolTip('Turn transcript on/off')
        grid.addWidget(self.transcb, 3, 0)
        self.transcb.setChecked(True)
        
        # Add status fields
        self.__got_versions = False
        status_panel = QtGui.QWidget(w)
        grid.addWidget(status_panel, 4, 0)
        self.__addStatus(status_panel)
             
        w.setLayout(grid) 
    
    def set_metis_running(self, state):
        """
        Set the Metis state
        
        Arguments:
            state   -- True if metis discovery successful
           
        """
        
        if not state:
            self.startbtn.setEnabled(True)
            
    def run(self):
        """" Run this GUI fragment """
        
        #-----------------------------------------------------------------------
        # Get the default or saved state
        # State is of the following form
        # DEFAULT_STATE = {
        #       NUM_RX: 1,
        #        IQ_BLK_SZ: 1024,
        #        MIC_BLK_SZ: 1024,
        #        DUPLEX: False,
        #        POWER: {
        #            PWR_MODE: AUTO,
        #            PWR_IP: '192.168.1.100',
        #            PWR_USER: 'admin',
        #            PWR_PW: '12345678',
        #            PWR_OUTLET: 1,
        #        },
        #        AUDIO: {
        #            IN_SRC: HPSDR,
        #            IN_HOSTAPI: '',
        #            IN_DEV: '',
        #            OUT_SINK: HPSDR,
        #            ROUTING: {
        #                HPSDR:[[1, '', BOTH],],
        #                LOCAL: []   
        #            }
        #        }    
        #    }
        #
        
        #-----------------------------------------------------------------------
        # Run the GUI
        self.show()
        self.repaint()
        
        #-----------------------------------------------------------------------
        # Manage remote power
        if not self.__test_mode:
            if not self.__managePower(True):
                return
        
        #-----------------------------------------------------------------------
        # Set the UI state
        self.startbtn.setEnabled(True)
        self.statusBar().showMessage('')
    
    def powerDown(self):
        """ Turn off the HPSDR power if required """
        
        if not self.__test_mode:
            self.__managePower(False)
    
    def quit(self):
        """ User hit quit """
        
        # Stop Metis if we were running
        if self.__running == RUNNING:
            self.__metis_imp.stopMetis()
        # Manage power-down
        self.__managePower(False)
        
        # Close if we have ever run
        if self.__running != INITIAL:
            # Close the UI
            for tx in self.__transmitters:
                tx.closedown()
            for rx in self.__receivers:
                rx.closedown()
            self.__transmitters = []
            self.__receivers = []
            
            # Close displays
            self.__control_api.run_display(False)
            for key, unit_ob in self.__displays.items():
                unit_ob.close_display()
            self.__displays = {}
            
            # Close all DSP channels
            for ch_id in self.__channels[CH_TX]:
                ch_ob = self.__channels[CH_TX][ch_id]
                ch_ob.set_ch_state(CH_STATE_STOP, CH_TRANSITION_NOWAIT)
                ch_ob.close_channel()
            for ch_id in self.__channels[CH_RX]:
                ch_ob = self.__channels[CH_RX][ch_id]
                ch_ob.set_ch_state(CH_STATE_STOP, CH_TRANSITION_NOWAIT)
                ch_ob.close_channel()
            self.__channels = {CH_RX: {}, CH_TX: {}}
            
            # Stop the backend
            self.__control_api.stop_processing()           
                            
        # Update window position and save our own state and settings
        position = self.pos()
        self.__state[WINDOW_X] = position.x()
        self.__state[WINDOW_Y] = position.y()
        self.__state[WINDOW_WIDTH] = self.width()
        self.__state[WINDOW_HEIGHT] = self.height()
        persist.saveState(Control.STATE_FILE, self.__state)
        persist.saveCfg(Control.CONFIG_FILE, self.__cfg)
        # Quit the window
        QtCore.QCoreApplication.instance().quit()
    
    def closeEvent(self, event):
        """ User hit little x """
        
        reply = QtGui.QMessageBox.question(self, 'Quit?',
            "Are you sure to quit?", QtGui.QMessageBox.Yes | 
            QtGui.QMessageBox.No, QtGui.QMessageBox.No)
        if reply == QtGui.QMessageBox.Yes:
            self.quit()
        else:
            event.ignore()
     
    # Access methods
    def get_duplex (self):
        """ Return the duplex state """
        
        return self.__cfg[COMMON][DUPLEX]
    
    # PRIVATE ==================================================================
    
    # Interactors --------------------------------------------------------------
    def __addStatus(self, panel):
        """
        Add versions, ip address etc.
        
        Arguments:
            panel   --  within this panel
        """
        
        grid = QtGui.QGridLayout()
        panel.setStyleSheet(".QWidget {border-style: solid; border-color: gray; border-width: 1px;}")
        
        metis_label = QtGui.QLabel('Metis')
        grid.addWidget(metis_label, 0, 0, QtCore.Qt.AlignLeft)
        self.__metis_v_field = QtGui.QLabel('-')
        grid.addWidget(self.__metis_v_field, 0, 1)
        self.__metis_ip_field = QtGui.QLabel('-')
        grid.addWidget(self.__metis_ip_field, 0, 2, QtCore.Qt.AlignLeft)
        
        merc_label = QtGui.QLabel('Merc')
        grid.addWidget(merc_label, 1, 0, QtCore.Qt.AlignLeft)
        self.__merc_v_field = QtGui.QLabel('-')
        grid.addWidget(self.__merc_v_field, 1, 1)
        
        penny_label = QtGui.QLabel('Penny')
        grid.addWidget(penny_label, 2, 0, QtCore.Qt.AlignLeft)
        self.__penny_v_field = QtGui.QLabel('-')
        grid.addWidget(self.__penny_v_field, 2, 1)
        
        panel.setLayout(grid)
                
    # Power ---------------------------------------------------------------------
    def __managePower(self, on):
        """
        Manage the IP Power Device
        
        Arguments:
            on  -- True if turn on else off
        """
        
        self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Power management'))
        if on:
            # Connect to the ip power device and turn nominated outlet on
            if not self.__cfg[COMMON][POWER][PWR_MODE] == OFF: 
                powerOn = False
                skip = False
                ip9258.connect(self.__cfg[COMMON][POWER][PWR_IP], self.__cfg[COMMON][POWER][PWR_USER], self.__cfg[COMMON][POWER][PWR_PW])
                try:
                    poweredOn = ip9258.getState(self.__cfg[COMMON][POWER][PWR_IP], self.__cfg[COMMON][POWER][PWR_OUTLET])
                except Exception as e:
                    reply = QtGui.QMessageBox.question(self, 'IP Power Device',
                    "No IP Power Device found. If not present turn the MODE to OFF. Press YES to continue anyway.",  
                    QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
                    if reply == QtGui.QMessageBox.No:
                        return False
                    skip = True
                if not skip:
                    if self.__cfg[COMMON][POWER][PWR_MODE] == PROMPT and not poweredOn:
                        reply = QtGui.QMessageBox.question(self, 'Power on HPSDR?',
                        "Do you want to power on HPSDR?", QtGui.QMessageBox.Yes | 
                        QtGui.QMessageBox.No, QtGui.QMessageBox.No)
                        if reply == QtGui.QMessageBox.Yes:
                            powerOn = True
                    elif self.__cfg[COMMON][POWER][PWR_MODE] == AUTO and not poweredOn:
                        powerOn = True
                    elif self.__cfg[COMMON][POWER][PWR_MODE] == MANUAL and not poweredOn:
                        reply = QtGui.QMessageBox.question(self, 'HPSDR Remote Power?',
                        "HPSDR has no remote power and config says manual. Press YES to continue anyway.", QtGui.QMessageBox.Yes | 
                        QtGui.QMessageBox.No, QtGui.QMessageBox.No)
                        if reply == QtGui.QMessageBox.No:
                            return False
                    if powerOn:
                        self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Powering up HPSDR...'))
                        ip9258.powerOn(self.__cfg[COMMON][POWER][PWR_IP], self.__cfg[COMMON][POWER][PWR_OUTLET])
                        self.__remotePowered = True
                        # Allow hardware to settle
                        sleep(5)
        else:
            if self.__remotePowered:
                # Connect to the ip power device and turn nominated outlet off
                ip9258.connect(self.__cfg[COMMON][POWER][PWR_IP], self.__cfg[COMMON][POWER][PWR_USER], self.__cfg[COMMON][POWER][PWR_PW])
                try:
                    poweredOn = ip9258.getState(self.__cfg[COMMON][POWER][PWR_IP], self.__cfg[COMMON][POWER][PWR_OUTLET])
                except Exception as e:
                    return False
                if poweredOn:
                    reply = QtGui.QMessageBox.question(self, 'Power off HPSDR?',
                    "Do you want to power off HPSDR?", QtGui.QMessageBox.Yes | 
                    QtGui.QMessageBox.No, QtGui.QMessageBox.No)
                    if reply == QtGui.QMessageBox.Yes:
                        self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Powering down HPSDR...'))
                        ip9258.powerOff(self.__cfg[COMMON][POWER][PWR_IP], self.__cfg[COMMON][POWER][PWR_OUTLET])
        
        return True
                           
    # UI Actions ---------------------------------------------------------------
    def __configEvnt(self, event):
        """
        Run the configurator
        
        Arguments:
            event   -- ui event object
            
        """
        
        self.__cfg, r = control_config.ConfigDialog.getConfig(self.__cfg, self.__applyCallback)
        if r:
            # If Ok save the new config but don't undate internally
            persist.saveCfg(Control.CONFIG_FILE, self.__cfg)
    
    def __applyCallback(self, config, hints):
        """
        Apply config changes
        
        Arguments:
            config  --  new configuration
            hints   --  whats changed
               
        """
        
        self.__cfg = config
        self.__hints = hints
        persist.saveCfg(Control.CONFIG_FILE, self.__cfg)
        # We don't change the global configuration
        # If anyone is interested they can get the new config from the instance cache 
        instance_cache.addToCache('Settings', self.__cfg)
        # Run the update off a timer event and not the button press event
        QtCore.QTimer.singleShot(100, self.__dispatchEvnt)
    
    def __dispatchEvnt(self):
        """ Called by timer to dispatch events so we don't do this on a UI callback """
        
        # Send change events
        if  RATE_EVNT in self.__hints or\
            NUM_RX_EVNT in self.__hints or\
            BLK_SZ_EVNT in self.__hints or\
            DUPLEX_EVNT in self.__hints or\
            TX_ENABLE_EVNT in self.__hints:
                # Combine to one reset event for the moment as we don't want multiple resets.
                broker.send(RESET_EVNT, None)
        else:
            for hint in self.__hints:           
                broker.send(hint, None)
        
    def __toggleStart(self, checked):
       
        if checked:
            self.startbtn.setStyleSheet("QPushButton {background-color: rgb(46,84,35);  font: bold 20px}")
            self.__start()
            self.startbtn.setText('Stop')
        else:
            self.startbtn.setStyleSheet("QPushButton {background-color: rgb(140,21,38);  font: bold 20px}")
            self.__stop()
            self.startbtn.setText('Start')
            
    def __start(self):
        """
        Start the HPSDR system
        
        Arguments:
            event   -- ui event object
            
        """
        
        # Start processing steps:
        # 1. Create the required number of TX and RX channels and display units. Commands must be sent to the
        #   correct channel/unit objects according the TX or RX id.
        # 2. Add channels to a channel structure indexed by the TX/RX id and units to a display structure.
        # 3. Start the channels which makes them ready to process data blocks.
        # 4. Run up a UI window for each receiver and transmitter. As a UI starts it will initialise the channel.
        # 5. Start the pipeline which sets the decode/dsp/encode pipeline up ready to receive data blocks.
        # 6. Start Metis processing to provide data to the pipeline.
        # 7. Adjust the GUI
        #
        
        if self.__running == INITIAL:
            # Get our API instance
            if self.__control_api == None or self.__metis_api == None:
                self.__control_api = instance_cache.getInstance('Control_API')
                self.__metis_api = instance_cache.getInstance('Metis_API')
            # TX and RX channels
            # (1)
            if self.__cfg[COMMON][TX]:
                # TX enabled
                # Note on TX in and out rate are always out rate (48K)
                ch = DSPChannelApi(CH_TX, 0, self.__cfg[COMMON][IQ_BLK_SZ], self.__cfg[COMMON][MIC_BLK_SZ], self.__cfg[COMMON][OUT_RATE], self.__cfg[COMMON][OUT_RATE], 0.1, 0.1, 0.1, 0.1, self.__q)
                # (2)
                self.__channels[CH_TX][0] = ch
                # (3)
                ch.set_ch_state(CH_STATE_START, CH_TRANSITION_NOWAIT)
                # (4)
                tx = transmitter.Transmitter(0, ch, self.__q, self.__channels)
                self.__transmitters.append(tx)
                tx.run()
            # (1)
            for n in range(self.__cfg[COMMON][NUM_RX]):
                # Create the channel
                # TBD - the delays and slews should be configurable
                ch = DSPChannelApi(CH_RX, n, self.__cfg[COMMON][IQ_BLK_SZ], self.__cfg[COMMON][MIC_BLK_SZ], self.__cfg[COMMON][IN_RATE], self.__cfg[COMMON][OUT_RATE], 0.1, 0.1, 0.1, 0.1, self.__q)
                # (2)
                self.__channels[CH_RX][n] = ch
                # (3)
                ch.set_ch_state(CH_STATE_START, CH_TRANSITION_NOWAIT)
                # (4)
                rx = receiver.Receiver(n, ch, self.__q)
                self.__receivers.append(rx)
                # Now create the display unit and add to the collection
                unit = rx.create_display_unit(self.__cfg[COMMON][IQ_BLK_SZ])
                self.__displays[n] = unit
                rx.run()
            # (5)
            if not self.__control_api.start_processing(self.__cfg, self.__channels, self.__displays, self.__cc_callback, self.__test_mode):
                self.__q.put ((BOTH, logging.ERROR, 'py.control_ui', 'Failed to start processing!'))
                self.startbtn.setEnabled(False)
                return
            # Start the pipeline display processing
            self.__control_api.run_display(True)
            
            # We can safely run an update now the backend is running
            sleep(1.0)
            for rx in self.__receivers:
                rx.updateConfig()
            for tx in self.__transmitters:
                tx.updateConfig()
            
        # (6)
        if not self.__test_mode:
            self.__metis_addr = None
            self.__got_addr = False
            r, self.__metis_addr = self.__metis_imp.startMetis()
            if not r:
                self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Error starting Metis!'))
                self.startbtn.setEnabled(False)
                return
        # (7)
        self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Running...'))
        self.__running = RUNNING
        
    def __stop(self):
        """
        Teardown the HPSDR system
        
        Arguments:
            event   -- ui event object
            
        """
        
        # Stop processing steps:
        # 1. Stop Metis processing to stop the input data stream.
        # 2. Adjust the GUI
        #
        
        if self.__running == RUNNING:
            # (1)
            self.__metis_imp.stopMetis()
            # (2)
            self.startbtn.setEnabled(True)
            self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Stopped'))
            self.__running = STOPPED
            if len(self.__transmitters) > 0:
                self.__transmitters[0].ensure_rx()
    
    def __transcriptEvnt(self, state):
        """
        Turn transcript window on/off
        
        Arguments:
            event   -- ui event object
            
        """
        
        if state == QtCore.Qt.Checked:
            self.__q.put((SHOW, None, None, None))
        else:
            self.__q.put((HIDE, None, None, None))

    # Callbacks --------------------------------------------------------------
    def __cc_callback(self, data):
        """
        Callback with command and control data from HPSDR
        
        Arguments:
            data    -- cc data
        """
        
        # Extract the version numbers
        if not self.__got_versions:
            if data[0] & 0xF8 == 0:
                # C0 bits 7-3 are zero
                self.__metis_v_field.setText(str(data[4]/10))
                self.__merc_v_field.setText(str(data[2]/10))
                self.__penny_v_field.setText(str(data[3]/10))
                self.__got_versions = True
        if not self.__got_addr:
            if self.__metis_addr != None:
                self.__metis_ip_field.setText(self.__metis_addr)
                self.__got_addr = True
        
    # Publish/Subscribe Events -----------------------------------------------
    def process_event(self, event_type, event_data):
        """
        Process system events
        
        Arguments:
            event_type    --  managed events from defs.py
            event_data    --  call specific data
            
        """
        
        if  event_type == RESET_EVNT:
            self.__reset()            
        
    def __reset(self):
        """ Major change to the configuration that requires a reset """
        
        # Cleardown everything ==================================================
        self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Closing...'))
        
        # Stop Metis if we were running
        self.__metisWasRunning = False
        if self.__running == RUNNING:
            self.__metis_imp.stopMetis()
            self.__metisWasRunning = True
            
        # Close the UI
        for tx in self.__transmitters:
            tx.closedown()
        for rx in self.__receivers:
            rx.closedown()
        self.__transmitters = []
        self.__receivers = []
        
        # Close all display channels
        self.__control_api.run_display(False)
        for key, unit_ob in self.__displays.items():
            unit_ob.close_display()
        self.__displays = {}
        
        # Close all DSP channels
        for ch_id in self.__channels[CH_TX]:
            ch_ob = self.__channels[CH_TX][ch_id]
            ch_ob.set_ch_state(CH_STATE_STOP, CH_TRANSITION_NOWAIT)
            ch_ob.close_channel()
        for ch_id in self.__channels[CH_RX]:
            ch_ob = self.__channels[CH_RX][ch_id]
            ch_ob.set_ch_state(CH_STATE_STOP, CH_TRANSITION_NOWAIT)
            ch_ob.close_channel()
        self.__channels = {CH_RX: {}, CH_TX: {}}
        
        # Stop the backend
        self.__control_api.stop_processing()
        self.__running == INITIAL      
        
        # Restart everything ==================================================
        self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Restarting...'))
        
        # Start TX and RX processing
        if self.__cfg[COMMON][TX]:
            # TX enabled
            # Note on TX, in and out rate are always out rate (48K)
            ch = DSPChannelApi(CH_TX, 0, self.__cfg[COMMON][IQ_BLK_SZ], self.__cfg[COMMON][MIC_BLK_SZ], self.__cfg[COMMON][OUT_RATE], self.__cfg[COMMON][OUT_RATE], 0.1, 0.1, 0.1, 0.1, self.__q)
            self.__channels[CH_TX][0] = ch
            ch.set_ch_state(CH_STATE_START, CH_TRANSITION_NOWAIT)
            tx = transmitter.Transmitter(0, ch, self.__q, self.__channels)
            self.__transmitters.append(tx)
            tx.run()
        for n in range(self.__cfg[COMMON][NUM_RX]):
            # Create the channel
            # TBD - the delays and slews should be configurable
            ch = DSPChannelApi(CH_RX, n, self.__cfg[COMMON][IQ_BLK_SZ], self.__cfg[COMMON][MIC_BLK_SZ], self.__cfg[COMMON][IN_RATE], self.__cfg[COMMON][OUT_RATE], 0.1, 0.1, 0.1, 0.1, self.__q)
            self.__channels[CH_RX][n] = ch
            ch.set_ch_state(CH_STATE_START, CH_TRANSITION_NOWAIT)
            rx = receiver.Receiver(n, ch, self.__q)
            self.__receivers.append(rx)
            # Now create the display unit and add to the collection
            unit = rx.create_display_unit(self.__cfg[COMMON][IQ_BLK_SZ])
            self.__displays[n] = unit
            rx.run()
        
        # Start processing
        if not self.__control_api.start_processing(self.__cfg, self.__channels, self.__displays, self.__cc_callback, self.__test_mode):
            self.__q.put ((BOTH, logging.ERROR, 'py.control_ui', 'Failed to start processing!'))
            self.startbtn.setEnabled(False)
            return
        self.__control_api.run_display(True)
        
        # We can safely run an update now the backend is running
        sleep(1.0)
        for rx in self.__receivers:
            rx.updateConfig()
        for tx in self.__transmitters:
            tx.updateConfig()
            
        # Start Metis
        if self.__metisWasRunning:
            self.__metis_addr = None
            self.__got_addr = False
            r, self.__metis_addr = self.__metis_imp.startMetis()
            if not r:
                self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Error starting Metis!'))
                self.startbtn.setEnabled(False)
                return
            self.__running = RUNNING
        else:
            self.__running = STOPPED
            
        self.__q.put ((BOTH, logging.INFO, 'py.control_ui', 'Reset complete'))
        